/**
 ******************************************************************************
 * @file    httpc_resp.c
 * @author  Chavis.Chen
 * @version V1.0.0
 * @date    06-Dec-2018
 * @brief   This file defines http request implementation.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2018 Chavis.Chen.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */



#include "httpc_resp.h"

httpc_response_code_t httpc_response_code_tbl[HTTP_RESP_CODE_MAX] = {HTTP_RESP_CODE_LIST(HTTP_RESP_CODE_TBL_FILL)};
char * httpc_response_head_tbl[HTTP_RESP_HEAD_MAX] = {HTTP_RESP_HEAD_LIST(HTTP_RESP_HEAD_TBL_FILL)};

OSStatus httpc_response_wm_lock(httpc_response_t * httpc_response_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_response_ptr, exit, err = kParamErr);

	err = rtos_mutex_lock(httpc_response_ptr->wm_mgr.mutex, QUEC_WAIT_FOREVER);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_response_wm_unlock(httpc_response_t * httpc_response_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_response_ptr, exit, err = kParamErr);

	err = rtos_mutex_unlock(httpc_response_ptr->wm_mgr.mutex);
	require_noerr(err, exit);
	
exit:
	return err;
}

OSStatus httpc_response_new
(
	httpc_response_t * httpc_response_ptr,
	uint32 wm_size,
	uint32 wm_low_val,
	uint32 wm_high_val
)
{
	OSStatus err = kNoErr;

	void (* cbLow)(void *);
	void (* cbHigh)(void *);
	void (* cbEmpty)(void *);

	require_action(httpc_response_ptr, exit, err = kParamErr);

	cbLow = httpc_response_ptr->wm_mgr.cb.reached_low;
	cbHigh = httpc_response_ptr->wm_mgr.cb.reached_high;
	cbEmpty = httpc_response_ptr->wm_mgr.cb.wm_empty;

	memset(httpc_response_ptr, 0, sizeof(httpc_response_t));

	err = wm_init(&httpc_response_ptr->wm_mgr.handle, wm_size, wm_low_val, wm_high_val, cbLow, cbHigh, cbEmpty);
	require_noerr(err, exit);

	err = rtos_mutex_create(&httpc_response_ptr->wm_mgr.mutex);
	require_noerr(err, exit);

exit:
	if(err != kNoErr && httpc_response_ptr != NULL)
	{
		if(httpc_response_ptr->wm_mgr.handle != NULL)
		{
			wm_deinit(&httpc_response_ptr->wm_mgr.handle);
		}
		if(httpc_response_ptr->wm_mgr.mutex != NULL)
		{
			rtos_mutex_delete(httpc_response_ptr->wm_mgr.mutex);
		}
		
		memset(httpc_response_ptr, 0, sizeof(httpc_response_t));
	}
	return err;
}

OSStatus httpc_response_head_parse(httpc_response_t * httpc_response_ptr, uint32 head_wait_timeout_ms, bool require_drop_head)
{
	OSStatus 	err = kNoErr;
	char * 			head_end_pos = NULL;
	char * 			head_1st_field_pos = NULL;
	char *			head_field_name = NULL;
	char *			head_field_end = NULL;
	char * 			chr_space = NULL;
	char *			chr_colon = NULL;
	uint32			head_wait_time_ms = head_wait_timeout_ms;
	int				i = 0;
	int				strcmp_ret = -1;

	require_action(httpc_response_ptr, exit, err = kParamErr);

	do
	{
		/*> if started with "HTTP/1.1", then it's a http response package */
	
		if(strcmp_ret != 0)
		{
			httpc_response_wm_lock(httpc_response_ptr);
			
			strcmp_ret = strncasecmp(httpc_response_ptr->wm_mgr.handle->ringBuffer->buffer, HTTP_PROT_VER, strlen(HTTP_PROT_VER));

			httpc_response_wm_unlock(httpc_response_ptr);
		}
		
		if(strcmp_ret == 0)
			goto next;
		else if(head_wait_time_ms != 0)
			goto wait;
		else
			break;
		
		/*> find the end string "\r\n\r\n" of http response head */
		
	next:

		httpc_response_wm_lock(httpc_response_ptr);
		
		head_end_pos = strstr(httpc_response_ptr->wm_mgr.handle->ringBuffer->buffer, HTTP_RESP_HEAD_WHOLE_END_STR);

		httpc_response_wm_unlock(httpc_response_ptr);

		if(head_end_pos != NULL || head_wait_time_ms == 0)
			break;

	wait:
		if(head_wait_time_ms == QUEC_WAIT_FOREVER)
		{
			rtos_task_sleep_ms(50);
		}
		else if(head_wait_time_ms >= 50)
		{
			rtos_task_sleep_ms(50);
			head_wait_time_ms -= 50;
		}
		else
		{
			rtos_task_sleep_ms(head_wait_time_ms);
			head_wait_time_ms = 0;
		}
	}while(1);

	require_action(strcmp_ret == 0, exit, err = kVersionErr);
	require_action(head_end_pos, exit, err = kFormatErr);

	/*> malloc a block of memory to save the http response head */
	httpc_response_ptr->head.wholePkt.len = head_end_pos - httpc_response_ptr->wm_mgr.handle->ringBuffer->buffer + 4;
	httpc_response_ptr->head.wholePkt.ptr = malloc(httpc_response_ptr->head.wholePkt.len + 1);

	httpc_log("httpc_response_ptr->head.wholePkt.len = %d", httpc_response_ptr->head.wholePkt.len);
	
	require_action(httpc_response_ptr->head.wholePkt.ptr, exit, err = kNoMemoryErr);

	memset(httpc_response_ptr->head.wholePkt.ptr, 0, httpc_response_ptr->head.wholePkt.len + 1);

	if(require_drop_head)
	{
		httpc_response_wm_lock(httpc_response_ptr);

		err = wm_read(&httpc_response_ptr->wm_mgr.handle, httpc_response_ptr->head.wholePkt.ptr, httpc_response_ptr->head.wholePkt.len, NULL, httpc_response_ptr->wm_mgr.handle);
		require_noerr(err, exit);
		
		httpc_response_wm_unlock(httpc_response_ptr);
	}
	else
	{
		memcpy(httpc_response_ptr->head.wholePkt.ptr, httpc_response_ptr->wm_mgr.handle->ringBuffer->buffer, httpc_response_ptr->head.wholePkt.len);
	}

	chr_space = strchr(httpc_response_ptr->head.wholePkt.ptr, CHR_SPACE);

	httpc_response_ptr->head.code.ptr = httpc_skip_spaces(chr_space);

	chr_space = strchr(httpc_response_ptr->head.code.ptr, CHR_SPACE);

	* chr_space = '\0';

	httpc_response_ptr->head.code.len = strlen(httpc_response_ptr->head.code.ptr);	/*> actually, this length must be 3 */

	head_1st_field_pos = strstr(chr_space + 1, HTTP_RESP_HEAD_FIELD_END_STR) + 2;

	memset(httpc_response_ptr->head.field, 0, sizeof(httpc_response_ptr->head.field));
	
	for(i=0; i<HTTP_RESP_HEAD_MAX; i++)
	{
		head_field_name = strcasestr(head_1st_field_pos, httpc_response_head_tbl[i]);

		if(head_field_name)
		{
			chr_colon = strchr(head_field_name, CHR_COLON);
			
			httpc_response_ptr->head.field[i].ptr = httpc_skip_spaces(chr_colon + 1);

			head_field_end = strstr(httpc_response_ptr->head.field[i].ptr, HTTP_RESP_HEAD_FIELD_END_STR);
			
			httpc_response_ptr->head.field[i].len = head_field_end - httpc_response_ptr->head.field[i].ptr;
		}
	}

	for(i=0; i<HTTP_RESP_HEAD_MAX; i++)
	{
		if(httpc_response_ptr->head.field[i].ptr)
			httpc_response_ptr->head.field[i].ptr[httpc_response_ptr->head.field[i].len] = '\0';
	}
	
exit:
	return err;
}

httpc_data_t * httpc_response_head_get_field(httpc_response_t * httpc_response_ptr, httpc_response_head_e httpc_response_head)
{
	return ((httpc_response_ptr == NULL || httpc_response_head >= HTTP_RESP_HEAD_MAX) ? NULL : &httpc_response_ptr->head.field[httpc_response_head]);
}

int httpc_response_head_get_code(httpc_response_t * httpc_response_ptr)
{
	return ((httpc_response_ptr == NULL || httpc_response_ptr->head.code.ptr == NULL) ? -1 : atoi(httpc_response_ptr->head.code.ptr));
}

char * httpc_response_head_get_code_meaning(httpc_response_t * httpc_response_ptr)
{
	httpc_response_code_e	code_index = 0;

	if(httpc_response_ptr == NULL || httpc_response_ptr->head.code.ptr == NULL)
		return NULL;

	for(; code_index < HTTP_RESP_CODE_MAX; code_index++)
	{
		if(strcasecmp(httpc_response_ptr->head.code.ptr, httpc_response_code_tbl[code_index].code) == 0)
			return httpc_response_code_tbl[code_index].meaning;
	}

	return NULL;
}

int httpc_response_get_whole_pkt_length(httpc_response_t * httpc_response_ptr)
{
	httpc_data_t * 	head_content_length = NULL;
	int				content_length = 0;
	
	head_content_length = httpc_response_head_get_field(httpc_response_ptr, HTTP_RESP_HEAD_CONTENT_LENGTH);

	if(head_content_length == NULL)
		return -1;

	if(str2int32(head_content_length->ptr, head_content_length->len, &content_length) == FALSE)
		return -1;

	httpc_log("content_length = %d", content_length);
	httpc_log("httpc_response_ptr->head.wholePkt.len = %d", httpc_response_ptr->head.wholePkt.len);

	httpc_log("whole pkt length = %d", content_length + httpc_response_ptr->head.wholePkt.len);

	return content_length + httpc_response_ptr->head.wholePkt.len;
}

OSStatus httpc_response_body_read(httpc_response_t * httpc_response_ptr, uint8 * body_buf, uint32 toReadLen, uint32 * pActualReadLen, bool * isTotallyRead)
{
	OSStatus 	err = kNoErr;
	httpc_data_t *	http_head_content_length = NULL;
	
	require_action(httpc_response_ptr, exit, err = kParamErr);

	httpc_response_wm_lock(httpc_response_ptr);
	
	err = wm_read(&httpc_response_ptr->wm_mgr.handle, body_buf, toReadLen, pActualReadLen, httpc_response_ptr->wm_mgr.handle);
	require_noerr(err, exit);

	httpc_response_wm_unlock(httpc_response_ptr);

	httpc_response_ptr->read_len += * pActualReadLen;

	httpc_log("body read sAlreadyReadLen = %d", httpc_response_ptr->read_len);

	http_head_content_length = httpc_response_head_get_field(httpc_response_ptr, HTTP_RESP_HEAD_CONTENT_LENGTH);
	require_action(http_head_content_length->ptr, exit, err = kResponseErr);

	httpc_log("content_length = %d", atoi(http_head_content_length->ptr));

	httpc_log("isTotallyRead = 0x%p", isTotallyRead);
	
	if(httpc_response_ptr->read_len >= atoi(http_head_content_length->ptr))
	{
		if(isTotallyRead)
		{
			* isTotallyRead = TRUE;
			
			httpc_log("* isTotallyRead = %d", * isTotallyRead);
		}

		httpc_response_ptr->read_len = 0;
	}
	else
	{
		if(isTotallyRead)
		{
			* isTotallyRead = FALSE;
			
			httpc_log("* isTotallyRead = %d", * isTotallyRead);
		}
	}

exit:

	if(err != kNoErr)
		httpc_response_ptr->read_len = 0;
	
	return err;
}

OSStatus httpc_response_whole_read(httpc_response_t * httpc_response_ptr, uint8 * whole_buf, uint32 toReadLen, uint32 * pActualReadLen, bool * isTotallyRead)
{
	OSStatus 	err = kNoErr;
	httpc_data_t *	http_head_content_length = NULL;
	
	require_action(httpc_response_ptr, exit, err = kParamErr);

	httpc_response_wm_lock(httpc_response_ptr);
	
	err = wm_read(&httpc_response_ptr->wm_mgr.handle, whole_buf, toReadLen, pActualReadLen, httpc_response_ptr->wm_mgr.handle);
	require_noerr(err, exit);

	httpc_response_wm_unlock(httpc_response_ptr);

	httpc_response_ptr->read_len += * pActualReadLen;

	httpc_log("whole read sAlreadyReadLen = %d", httpc_response_ptr->read_len);
	
	http_head_content_length = httpc_response_head_get_field(httpc_response_ptr, HTTP_RESP_HEAD_CONTENT_LENGTH);
	require_action(http_head_content_length->ptr, exit, err = kResponseErr);

	httpc_log("isTotallyRead = 0x%p", isTotallyRead);
	
	if(httpc_response_ptr->read_len >= httpc_response_get_whole_pkt_length(httpc_response_ptr))
	{
		if(isTotallyRead)
		{
			* isTotallyRead = TRUE;

			httpc_log("* isTotallyRead = %d", * isTotallyRead);
		}
		httpc_response_ptr->read_len = 0;
	}
	else
	{
		if(isTotallyRead)
		{
			* isTotallyRead = FALSE;

			httpc_log("* isTotallyRead = %d", * isTotallyRead);
		}
	}

exit:

	if(err != kNoErr)
		httpc_response_ptr->read_len = 0;
	
	return err;
}

OSStatus httpc_response_destroy(httpc_response_t * httpc_response_ptr)
{
	OSStatus err = kNoErr;

	require_action(httpc_response_ptr, exit, err = kParamErr);

	if(httpc_response_ptr->head.wholePkt.ptr)
	{
		free(httpc_response_ptr->head.wholePkt.ptr);
	}

	if(httpc_response_ptr->wm_mgr.handle != NULL)
	{
		wm_deinit(&httpc_response_ptr->wm_mgr.handle);
	}
	
	if(httpc_response_ptr->wm_mgr.mutex != NULL)
	{
		rtos_mutex_delete(httpc_response_ptr->wm_mgr.mutex);
	}

	memset(httpc_response_ptr, 0, sizeof(httpc_response_t));

exit:
	return err;
}

